PHP 正则表达式笔记

什么是正则表达式

在电脑上我们经常会使用(通配符)找出我们需要的文件,例如:*.doc ,这里的 * 代表匹配零个或多个字符。正则表达式也是用来进行文本匹配的工具,只不过它更加强悍。引用 PHP 手册里的一句话:正则表达式是一个从左到右匹配目标字符串的模式,大多数字符自身就代表一个匹配 它们自身的模式。

下面给出几个简单例子,使对正则表达式有个初步的理解。

1
hi  //匹配英文字符(忽略大小写) hi , HI , Hi , hI
1
\bhi\b  //匹配英文单词 hi  '\b'是正则里的一特殊字符(一种断言),表示单词边界
1
\bhi\b.*\bLucy\b  //匹配如:'hi my name is Lucy'  '.' 表示匹配除换行符以外的任意字符  '*' 是量词,表示重复零次或更多次
1
0\d{2}-\d{8}  //匹配如: 020-12345678  '\d' 匹配一个数字(0-9)    '{n}' 重复n次,如{2} {8}

上面例子中的 \b , . , * , \d , {2} 都有特殊含义,在下文会有说明。

PHP 中正则语法

1.简介

在 PHP 里支持两种正则分别是 POSIX 和 PCRE 。自 PHP 5.3.0起,POSIX 正则表达式扩展被废弃。所以下文讨论的都是基于 PCRE 模式。可点击查看有关与 POSIX 正则表达式的不同与 perl 的不同之处

2.分隔符

当使用 PCRE 函数 的时候,模式需要由分隔符闭合包裹。分隔符可以使任意非字母数字、非反斜线、非空白字符。经常使用的分隔符是正斜线 / 、hash符号 # 以及取反符号 ~ 。下面的例子都是使用合法分隔符的模式。

1
2
3
4
/foo bar/
#^[^0-9]$#
+php+
%[a-zA-Z0-9_-]%

如果分隔符需要在模式内进行匹配,它必须使用反斜线进行转义。如果分隔符经常在模式内出现,一个更好的选择就是是用其他分隔符来提高可读性。例:

1
2
/http:\/\//
#http://#

3.元字符

正则表达式的威力源于它可以在模式中拥有选择和重复的能力。一些字符被赋予特殊的含义,使其不再单纯的代表自己,模式中的这种有特殊涵义的编码字符 称为元字符
共有两种不同的元字符:一种是可以在模式中方括号外任何地方使用的,另外一种是需要在方括号内使用的。

在方括号外使用的元字符如下:

代码 说明
/ 一般用于转义字符
^ 断言目标的开始位置(或在多行模式下是行首)
$ 断言目标的结束位置(或在多行模式下是行尾)
. 匹配除换行符外的任何字符(默认)
[ 开始字符类定义
] 结束字符类定义
| 开始一个可选分支
( 子组的开始标记
) 子组的结束标记
? a:作为量词,表示 0 次或 1 次匹配。b:位于量词后面用于改变量词的贪婪特性。
* 量词,0 次或多次匹配
+ 量词,1 次或多次匹配
{ 自定义量词开始标记
} 自定义量词结束标记

模式中方括号内的部分称为“字符类”。 在一个字符类中仅有以下可用元字符:

代码 说明
\ 转义字符
^ 仅在作为第一个字符(方括号内)时,表明字符类取反
- 标记字符范围

示例:

  • \ba\w*\b 匹配以字母 a 开头的单词,先是某个单词开始处 \b ,然后是字母 a ,然后是任意数量的任意单词字符(单词字符指的是任意字母、数字、下划线) \w* ,最后是单词结束处 \b 。
  • \d+ 匹配1个或更多连续的数字。
  • ^\d{5,12}$ 匹配为5位到12位数字,因为使用了 ^ 和 $ ,所以输入的整个字符串都要用来和 \d{5,12} 来匹配,也就是说整个输入必须是5到12个数字。

4.转义序列(反斜线)

反斜线 \ 有四种用法,详细可点击 转义序列(反斜线)

【1】作为转义字符,比如,如果你希望匹配一个 * 字符,就需要在模式中写为 \* 。这适用于一个字符在不进行转义会有特殊含义的情况下。 但是,对于非数字字母的字符,总是在需要其进行原文匹配的时候在它前面增加一个反斜线,来声明它代表自己,这是安全的。如果要匹配一个反斜线,那么在模式中使用 \\
反斜线在单引号字符串和双引号字符串中都有特殊含义,因此要匹配一个反斜线, 模式中必须写为 \\\\ 。其中的原因:首先它作为字符串,反斜线会进行转义。最后正则表达式引擎也认为反斜线是转义。因此,需要 4 个反斜线才可以匹配一个反斜线。

【2】提供了一种对非打印字符进行可见编码的控制手段

【3】用来描述特定的字符类

代码 说明
\d 任意十进制数字
\D 任意非十进制数字
\h 任意水平空白字符(since PHP 5.2.4)
\H 任意非水平空白字符(since PHP 5.2.4)
\s 任意空白字符
\S 任意非空白字符
\v 任意垂直空白字符(since PHP 5.2.4)
\V 任意非垂直空白字符(since PHP 5.2.4)
\w 任意单词字符,单词字符指的是任意字母、数字、下划线。
\W 任意非单词字符

【4】一些简单的断言。一个断言指定一个必须在特定位置匹配的条件,它们不会从目标字符串中消耗任何字符。反斜线断言包括:

  • \b 单词边界
  • \B 非单词边界
  • \A 目标的开始位置(独立于多行模式)
  • \Z 目标的结束位置或结束处的换行符(独立于多行模式)
  • \z 目标的结束位置(独立于多行模式)
  • \G 在目标中首次匹配位置

5.重复/量词

代码 说明
* 重复零次或更多次,等价于 {0,}
+ 重复一次或更多次,等价于 {1,}
? 重复零次或一次,等价于 {0,1}
{n} 重复n次
{n,} 重复n次或更多次
{n,m} 重复n到m次

默认情况下,量词都是”贪婪”的,也就是说,它们会在不导致模式匹配失败的前提下,尽可能多的匹配字符(直到最大允许的匹配次数)。然而,如果一个量词紧跟着一个 ? 标记,它就会成为懒惰(非贪婪)模式, 它不再尽可能多的匹配,而是尽可能少的匹配。
下面直接看示例,理解“贪婪”和“非贪婪”模式是怎么回事。

1
2
3
4
5
对于字符串 "aa<div>test1</div>bb<div>test2</div>cc"

正则表达式 "<div>.*</div>" 匹配结果 "<div>test1</div>bb<div>test2</div>"

正则表达式 "<div>.*?</div>" 匹配结果 "<div>test1</div>"

关于更多“贪婪”和“非贪婪”模式的介绍可查阅 http://php.net/manual/zh/regexp.reference.repetition.php

6.字符类(方括号)

PHP手册中的描述:

  • 左方括号开始一个字符类的描述,并以方中括号结束。单独的一个右方括号没有特殊含义。如果一个右方括号需要作为一个字符类中的成员,那么可以将它写在字符类的首字符处(如果使用了 ^ 取反,那么是第二个)或者使用转义符。

  • 一个字符类在目标字符串中匹配一个单独的字符;该字符必须是字符类中定义的字符集合的其中一个, 除非使用了 ^ 对字符类取反。如果^需要作为一个字符类的成员,确保它不是该字符类的首字符,或者对其进行转义即可。

示例:

1
2
3
4
5
[aeiou]    //匹配所有的小写元音字母

[^aeiou] //匹配所有非元音字母的字符

[.?!] //匹配标点符号(.或?或!)

注意:^ 只是一个通过枚举指定那些不存在字符类之中的字符的便利符号。而不是断言, 它仍然会从目标字符串中消耗一个字符,并且如果当前匹配点在目标字符串末尾, 匹配将会失败。

轻松地指定一个字符范围,范围操作以 ASCII 整理排序。它们可以用于为字符指定数值,比如 [\000-\037]

1
2
[0-9]    //代表的含意与 '\d' 就是完全一致的
[a-z0-9A-Z_] //完全等同于 '\w' 如果只考虑英文的话

下面是一个更复杂的表达式 \(?0\d{2}[) -]?\d{8}
这个表达式可以匹配几种格式的电话号码,像 (010)88886666,或 022-22334455 ,或 02912345678 等。
简单分析:首先是一个转义字符 \( ,它能出现 0 次或 1 次 ? ,然后是一个数字 0 ,后面跟着 2 个数字 \d{2} ,然后是 )- 或 “空格” 中的一个,它出现 0 次或 1 次,最后是 8 个数字 \d{8}

7.分支 ( | )

竖线字符用于分离模式中的可选路径。比如模式 gilbert|Sullivan 匹配 ”gilbert” 或者 ”sullivan”。竖线可以在模式中出现任意多个,并且允许有空的可选路径(匹配空字符串)。匹配的处理从左到右尝试每一个可选路径,并且使用第一个成功匹配的。如果可选路径在子组(下面定义)中,则”成功匹配”表示同时匹配了子模式中的分支以及主模式中的其他部分。

回看上文里的一个例子 \(?0\d{2}[) -]?\d{8} 这个正则也能匹配 010)12345678 或 (022-87654321 这样的 “不正确” 的格式。其实我们可以利用分支就能解决这个问题,如下:

\({1}0\d{2}\){1}[- ]?\d{8}|0\d{2}[- ]?\d{8} 这个表达式匹配 3 位区号的电话号码,其中区号可以用小括号括起来,也可以不用,区号与本地号间可以用连字号或空格间隔,也可以没有间隔。

使用分枝条件时,要注意各个条件的顺序

8.内部选项设置

正则表达式在不同的模式修饰符下匹配出的结果有可能不相同。它的语法是 :(?修饰符)

比如,(?im) 设置表明多行大小写不敏感匹配。同样可以用它来取消这些设置,比如 (?im-sx) 设置了 “PCRE_CASELESS”,”PCRE_MULTILINE”,但是同时取消了 “PCRE_DOTALL” 和 “PCRE_EXTENDED”。如果一个字母即出现在 - 之前, 也出现在 - 之后,这个选项被取消设置。

下面紧例举简单的示例,想要了解更多可点击 内部选项设置模式修饰符

示例:/ab(?i)c/ 仅仅匹配 ”abc” 和 ”abC”

9.子组(子模式)

子组通过圆括号分隔界定,并且它们可以嵌套。

示例:

1
2
3
4
字符串:"the red king"
正则表达式:((red|white) (king|queen))
匹配结果:array("red king", "red king", "red", "king")
描述:其中第 0 个元素是整个模式匹配的结果,后面的三个元素依次为三个子组匹配的结果。 它们的下标分别为 1, 2, 3。

经常我们会有一种需求需要使用子组进行分组,但又不需要(单独的)捕获它们。在子组定义的左括号后面紧跟字符串 ?: 会使得该子组不被单独捕获,并且不会对其后子组序号的计算产生影响。例如:

1
2
3
字符串:"the red king"
正则表达式:((?:red|white) (king|queen))
匹配结果:array("red king", "red king", "king")

为了方便简写,如果需要在非捕获子组开始位置设置选项, 选项字母可以位于 ?: 之间,比如:

1
2
(?i:saturday|sunday)
(?:(?i)saturday|sunday)

上面两种写法实际上是相同的模式。因为可选分支会从左到右尝试每个分支,并且选项没有在子模式结束前被重置,并且由于选项的设置会穿透对后面的其他分支产生影响,因此, 上面的模式都会匹配 ”SUNDAY” 以及 ”Saturday”。

再看一个匹配 IP 地址的正则 ((2[0-4]\d|25[0-5]|[01]?\d\d?)\.){3}(2[0-4]\d|25[0-5]|[01]?\d\d?)
相关文章 IP地址的正则表达式

结语

上文中涉及 PHP 正则表达式中常用的语法,有的语法没细说和涉及到的,如:模式修饰符、后向引用、断言、递归模式,等。你可以通过 PHP 手册查看这些内容。

提示:一般而言,对于同样的功能,正则表达式函数运行效率要低于字符串函数。如果应用程序较简单,那么就用字符串表达式。但是,对于可以通过单个正则表达式执行的任务来说,如果使用多个字符串函数,则是不对的。—- 摘自《PHP 和 MySQL Web 开发》一书。

相关阅读